using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
using CriticalSystems.Instrumentation.Configuration;
using CriticalSystems.Instrumentation.Internal;

namespace CriticalSystems.Instrumentation.TraceWriters
{
    internal class FileTraceWriter : ITraceWriter
    {
        private const string FileNameTimeFormat = "yyyyMMddTHHmmss";
        private readonly TimeSpan bufferWriteInterval;
        private readonly DateTime created;
        private readonly string filePath;
        private readonly int maxBufferSize;
        private readonly int maxFileSize;
        private readonly TraceFileManagement traceFileManagement;
        private readonly Queue<string> writeQueue;
        private readonly Timer writeTimer;
        private int bufferSize;
        private bool disposed;

        public FileTraceWriter(string filePath, TraceFileManagement traceFileManagement, int maxFileSize,
                               int maxBufferSize, TimeSpan bufferWriteInterval)
        {
            this.writeQueue = new Queue<string>(1000);
            this.filePath = filePath;
            this.traceFileManagement = traceFileManagement;
            this.maxFileSize = maxFileSize;
            this.maxBufferSize = maxBufferSize;
            this.bufferWriteInterval = bufferWriteInterval;
            this.writeTimer = new Timer(OnWriteTimer, null, this.bufferWriteInterval, TimeSpan.FromMilliseconds(-1));
            this.created = DateTime.Now;
            ManageTraceFile();
        }

        #region ITraceWriter Members
        public TracingFlags Flags
        {
            get 
            {
                return InstrumentationConfiguration.Instance.Tracing;
            }
        }
        public void Write(TraceType traceType, int indent, string instance, string type, string method, string text)
        {
            throw new NotImplementedException();
        }

        public void Write(string text)
        {
            if (text == null)
            {
                throw new ArgumentNullException("text");
            }
            if (text.Length > this.maxBufferSize)
            {
                text = text.Substring(0, this.maxBufferSize);
            }

            if (this.bufferSize + text.Length > this.maxBufferSize)
            {
                this.writeTimer.Change(0, 0);
            }
            lock (this.writeQueue)
            {
                this.writeQueue.Enqueue(text);
                Interlocked.Add(ref this.bufferSize, text.Length);
            }
        }

        public bool SimpleWriter
        {
            get { return true; }
        }
        #endregion

        ~FileTraceWriter()
        {
            lock (this.writeTimer)
            {
                this.disposed = true;
                this.writeTimer.Dispose();
            }
            lock (this.writeQueue)
            {
                WriteQueueToFile();
            }
        }

        private void ManageTraceFile()
        {
            switch (this.traceFileManagement)
            {
                case TraceFileManagement.ArchiveOld:
                    ArchiveTraceFile();
                    CreateTraceFile().Close();
                    break;
                case TraceFileManagement.DeleteOld:
                    DeleteTraceFiles();
                    CreateTraceFile().Close();
                    break;
            }
        }

        private void DeleteTraceFiles()
        {
            string deletefileNames = string.Format(CultureInfo.InvariantCulture, "{0}*{1}",
                                                   Path.GetFileNameWithoutExtension(this.filePath),
                                                   Path.GetExtension(this.filePath));
            string[] files = Directory.GetFiles(Path.GetDirectoryName(this.filePath), deletefileNames);
            foreach (string file in files)
            {
                File.Delete(file);
            }
        }

        private void ArchiveTraceFile()
        {
            if (File.Exists(this.filePath))
            {
                FileInfo fi = new FileInfo(this.filePath);
                string archiveFileName = string.Format(CultureInfo.InvariantCulture, "{0}_{1}-{2}{3}",
                                                       Path.GetFileNameWithoutExtension(this.filePath),
                                                       fi.CreationTime.ToString(FileNameTimeFormat),
                                                       fi.LastWriteTime.ToString(FileNameTimeFormat), fi.Extension);
                fi.MoveTo(Path.Combine(Path.GetDirectoryName(this.filePath), archiveFileName));
            }
        }

        private void OnWriteTimer(object state)
        {
            lock (this.writeTimer)
            {
                if (!this.disposed)
                {
                    WriteQueueToFile();
                    this.writeTimer.Change(this.bufferWriteInterval, TimeSpan.FromMilliseconds(-1));
                }
            }
        }

        private void WriteQueueToFile()
        {
            while (this.writeQueue.Count > 0)
            {
                StringBuilder builder = new StringBuilder();
                while (builder.Length < this.maxBufferSize && this.writeQueue.Count > 0)
                {
                    string text;
                    lock (this.writeQueue)
                    {
                        text = this.writeQueue.Dequeue();
                    }
                    Interlocked.Add(ref this.bufferSize, -text.Length);
                    builder.AppendLine(text);
                }
                if (builder.Length > 0)
                {
                    WriteBufferToFile(builder.ToString());
                }
            }
        }

        private void WriteBufferToFile(string data)
        {
            FileStream activeFile = null;
            try
            {
                activeFile = new FileStream(this.filePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
                if (this.maxFileSize > 0 &&
                    (this.traceFileManagement == TraceFileManagement.DeleteOld ||
                     this.traceFileManagement == TraceFileManagement.ArchiveOld) &&
                    activeFile.Length + data.Length > this.maxFileSize)
                {
                    activeFile.Close();
                    ArchiveTraceFile();
                    activeFile = CreateTraceFile();
                }
                using (StreamWriter writer = new StreamWriter(activeFile))
                {
                    writer.Write(data);
                }
            }
            finally
            {
                if (activeFile != null)
                {
                    activeFile.Close();
                }
            }
        }

        private FileStream CreateTraceFile()
        {
            FileStream stream = new FileStream(this.filePath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)
                ;
            FileInfo fi = new FileInfo(this.filePath);
            fi.CreationTime = this.created;
            return stream;
        }
    }
}